{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "16bb4bc3",
   "metadata": {},
   "source": [
    "# 计划和执行\n",
    "\n",
    "计划和执行代理通过首先计划要做什么，然后执行子任务来实现目标。 这个想法很大程度上受到[BabyAGI](https://github.com/yoheinakajima/babyagi)和[“Plan-and-Solve”论文](https://arxiv.org/abs/2305.04091)的启发。\n",
    "\n",
    "规划几乎总是由LLM完成。\n",
    "\n",
    "执行通常由单独的代理（配备工具）完成。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "50b5ef2f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'[snippet: 侦探娱乐. 2023-08-25 23:11 发布于 四川. + 关注. 8月25日晚，刘亦菲在个人社交平台分享了一组36岁生日现场照，虽然没有配任何文字，但一连串的爱心、生日蛋糕等小表情却将刘亦菲当下的好心情完美展示。. 看得出来，今年的生日，刘亦菲依旧是开心到飞 ..., title: 刘亦菲晒36岁生日现场照，被鲜花玩偶簇拥，嘟嘴卖萌开心 ..., link: https://new.qq.com/rain/a/20230825A0AFTV00], [snippet: 刘亦菲作为该品牌全球代言人，此次亮相依旧是闪耀全场，身着LV2024春夏系列小黑裙亮相，复古摩登又不失俏皮，在巴黎的夜色下，精致的如同贵族公主。, title: 巴黎LV秀场：Lisa妆容油腻，刘亦菲白丝袜引群嘲，宋茜赢得 ..., link: https://new.qq.com/rain/a/20240306A03NCJ00], [snippet: 推荐刘亦菲的电影排行榜：（主要按照观赏性排名） 1.五月之恋。 两岸情怀寄于一段清新爱情故事，既有宝岛风土，又有北国风光，既有年轻人的明媚爱恋，又有老一辈的淡淡乡愁。, title: 刘亦菲有哪些电影值得一看？ - 知乎, link: https://www.zhihu.com/question/629480279], [snippet: 近日，刘亦菲的表姐在个人社交账号上分享了一张与刘亦菲的同框自拍，照片一经发布，立即引发了广大网友的热烈讨论和关注。照片中，刘亦菲与表姐亲密贴贴，笑容满面，姐妹情深不言而喻，两人都展现出了极高的颜值和优雅气质，让人不禁赞叹天仙姐姐家的基因之强大。, title: 刘亦菲与表姐自拍大秀姐妹情，笑容甜美仙气四溢，家族基因 ..., link: https://new.qq.com/rain/a/20240324A06PO200]'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_community.utilities import DuckDuckGoSearchAPIWrapper\n",
    "from langchain_community.tools import DuckDuckGoSearchResults\n",
    "\n",
    "wrapper = DuckDuckGoSearchAPIWrapper(region=\"zh-cn\", max_results=2,source=\"news\")\n",
    "\n",
    "search = DuckDuckGoSearchResults(api_wrapper=wrapper, )\n",
    "search.run(\"刘亦菲\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0af14a1",
   "metadata": {},
   "source": [
    "## 加载LLM\n",
    "\n",
    "首先，让我们加载将用于控制代理的语言模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "016d09ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI, OpenAI\n",
    "\n",
    "openai_api_key = \"EMPTY\"\n",
    "openai_api_base = \"http://127.0.0.1:1234/v1\"\n",
    "model = ChatOpenAI(\n",
    "    openai_api_key=openai_api_key,\n",
    "    openai_api_base=openai_api_base,\n",
    "    temperature=0.3,\n",
    ")\n",
    "llm = model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "143aa5b4",
   "metadata": {},
   "source": [
    "## 定义工具\n",
    "\n",
    "接下来，让我们定义一些要使用的工具。让我们编写一个非常简单的 Python 函数来计算传入的单词的长度。\n",
    "\n",
    "请注意，这里我们使用的函数文档字符串非常重要。在此处阅读有关为什么会这样的更多信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fa2269a9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents import tool\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_word_length(word: str) -> int:\n",
    "    \"\"\"Returns the length of a word.\"\"\"\n",
    "    return len(word)\n",
    "\n",
    "\n",
    "get_word_length.invoke(\"abc\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "18c75770",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'description': '多云', 'temperature': '28'}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "@tool\n",
    "def get_weather(location):\n",
    "    \"\"\"根据城市获取天气数据\"\"\"\n",
    "    api_key = \"SKcA5FGgmLvN7faJi\"\n",
    "    url = f\"https://api.seniverse.com/v3/weather/now.json?key={api_key}&location={location}&language=zh-Hans&unit=c\"\n",
    "    response = requests.get(url)\n",
    "    #print(location)\n",
    "    if response.status_code == 200:\n",
    "        data = response.json()\n",
    "        #print(data)\n",
    "        weather = {\n",
    "            'description':data['results'][0][\"now\"][\"text\"],\n",
    "            'temperature':data['results'][0][\"now\"][\"temperature\"]\n",
    "        }\n",
    "        return weather\n",
    "    else:\n",
    "        raise Exception(f\"失败接收天气信息：{response.status_code}\")\n",
    "            \n",
    "get_weather.invoke(\"广州\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "07d31e30",
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool\n",
    "def search(keywords):\n",
    "    \"\"\"使用duckduckgo搜索引擎获取内容\"\"\"\n",
    "    return search.run(keywords)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "5a36f8df",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [get_word_length,get_weather,search]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3f6ea25",
   "metadata": {},
   "source": [
    "## 创建提示\n",
    "\n",
    "现在让我们创建提示。由于 OpenAI 函数调用针对工具使用进行了微调，因此我们几乎不需要任何关于如何推理或如何输出格式的说明。我们将只有两个输入变量： input 和 agent_scratchpad 。 input 应为包含用户目标的字符串。 agent_scratchpad 应该是包含先前代理工具调用和相应工具输出的消息序列。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5b49ce6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "promptTemplate = \"\"\"尽可能的帮助用户回答任何问题。\n",
    "\n",
    "您可以使用以下工具来帮忙解决问题，如果已经知道了答案，也可以直接回答：\n",
    "\n",
    "{tools}\n",
    "\n",
    "回复格式说明\n",
    "----------------------------\n",
    "\n",
    "回复我时，请以以下两种格式之一输出回复：\n",
    "\n",
    "选项 1：如果您希望人类使用工具，请使用此选项。\n",
    "采用以下JSON模式格式化的回复内容：\n",
    "\n",
    "```json\n",
    "{{\n",
    "    \"reason\": string, \\\\ 叙述使用工具的原因\n",
    "    \"action\": string, \\\\ 要使用的工具。 必须是 {tool_names} 之一\n",
    "    \"action_input\": string \\\\ 工具的输入\n",
    "}}\n",
    "````\n",
    "\n",
    "选项2：如果您认为你已经有答案或者已经通过使用工具找到了答案，想直接对人类做出反应，请使用此选项。 采用以下JSON模式格式化的回复内容：\n",
    "\n",
    "```json\n",
    "{{\n",
    "  \"action\": \"Final Answer\",\n",
    "  \"answer\": string \\\\最终答复问题的答案放到这里！\n",
    "}}\n",
    "````\n",
    "\n",
    "用户的输入\n",
    "--------------------\n",
    "这是用户的输入（请记住通过单个选项，以JSON模式格式化的回复内容，不要回复其他内容）：\n",
    "\n",
    "{input}\n",
    "\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "d2274f63",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"你是非常强大的助手，你可以使用各种工具来完成人类交给的问题和任务。\",\n",
    "        ),\n",
    "        (\"user\", promptTemplate),\n",
    "        MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "f1c3bfc2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptTemplate(input_variables=['agent_scratchpad', 'input'], input_types={'agent_scratchpad': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]}, partial_variables={'tools': 'get_word_length: get_word_length(word: str) -> int - Returns the length of a word.\\nget_weather: get_weather(location) - 根据城市获取天气数据\\nsearch: search(keywords) - 使用duckduckgo搜索引擎获取内容', 'tool_names': 'get_word_length, get_weather, search'}, messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='你是非常强大的助手，你可以使用各种工具来完成人类交给的问题和任务。')), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input', 'tool_names', 'tools'], template='尽可能的帮助用户回答任何问题。\\n\\n您可以使用以下工具来帮忙解决问题，如果已经知道了答案，也可以直接回答：\\n\\n{tools}\\n\\n回复格式说明\\n----------------------------\\n\\n回复我时，请以以下两种格式之一输出回复：\\n\\n选项 1：如果您希望人类使用工具，请使用此选项。\\n采用以下JSON模式格式化的回复内容：\\n\\n```json\\n{{\\n    \"reason\": string, \\\\ 叙述使用工具的原因\\n    \"action\": string, \\\\ 要使用的工具。 必须是 {tool_names} 之一\\n    \"action_input\": string \\\\ 工具的输入\\n}}\\n````\\n\\n选项2：如果您认为你已经有答案或者已经通过使用工具找到了答案，想直接对人类做出反应，请使用此选项。 采用以下JSON模式格式化的回复内容：\\n\\n```json\\n{{\\n  \"action\": \"Final Answer\",\\n  \"answer\": string \\\\最终答复问题的答案放到这里！\\n}}\\n````\\n\\n用户的输入\\n--------------------\\n这是用户的输入（请记住通过单个选项，以JSON模式格式化的回复内容，不要回复其他内容）：\\n\\n{input}\\n\\n')), MessagesPlaceholder(variable_name='agent_scratchpad')])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.tools.render import  render_text_description\n",
    "\n",
    "prompt = prompt.partial(\n",
    "        tools=render_text_description(list(tools)),\n",
    "        tool_names=\", \".join([t.name for t in tools]),\n",
    ")\n",
    "prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "31468772",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'get_word_length: get_word_length(word: str) -> int - Returns the length of a word.\\nget_weather: get_weather(location) - 根据城市获取天气数据\\nsearch: search(keywords) - 使用duckduckgo搜索引擎获取内容'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "render_text_description(list(tools))\n",
    "\n",
    "# get_word_length: get_word_length(word: str) -> int - Returns the length of a word.\n",
    "# get_weather: get_weather(location) - 根据城市获取天气数据\n",
    "# searxng_search: searxng_search(query) - 输入搜索内容，使用 SearXNG 进行搜索。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ff6d290",
   "metadata": {},
   "source": [
    "## 将工具绑定到LLM\n",
    "\n",
    "在这种情况下，我们依赖于 OpenAI 工具调用 LLMs，它将工具作为一个单独的参数，并经过专门训练，知道何时调用这些工具。\n",
    "\n",
    "要将我们的工具传递给代理，我们只需要将它们格式化为 OpenAI 工具格式并将它们传递给我们的模型。（通过 bind 对函数进行 -ing，我们确保每次调用模型时都传入它们。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b46c172a",
   "metadata": {},
   "source": [
    "## 创建代理\n",
    "\n",
    "将这些部分放在一起，我们现在可以创建代理。我们将导入最后两个实用程序函数：一个用于格式化中间步骤（代理操作、工具输出对）以输入可发送到模型的消息的组件，以及一个用于将输出消息转换为代理操作/代理完成的组件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "fe513870",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"TOOL RESPONSE: \\n---------------------\\n{observation}\\n\\nUSER'S INPUT\\n--------------------\\n\\nOkay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else - even if you just want to respond to the user. Do NOT respond with anything except a JSON snippet no matter what!\""
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents.json_chat.prompt import TEMPLATE_TOOL_RESPONSE\n",
    "\n",
    "TEMPLATE_TOOL_RESPONSE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "0d37af86",
   "metadata": {},
   "outputs": [],
   "source": [
    "TEMPLATE_TOOL_RESPONSE = \"\"\"工具响应：\n",
    "---------------------\n",
    "{observation}\n",
    "\n",
    "用户的输入：\n",
    "---------------------\n",
    "请根据工具的响应判断，是否能够回答问题：\n",
    "\n",
    "{input}\n",
    "\n",
    "请根据工具响应的内容，思考接下来回复。回复格式严格按照前面所说的2种JSON回复格式，选择其中1种进行回复。请记住通过单个选项，以JSON模式格式化的回复内容，不要回复其他内容。\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "18aa7260",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n",
    "def format_log_to_messages(\n",
    "    query,\n",
    "    intermediate_steps,\n",
    "    template_tool_response,\n",
    "):\n",
    "    \"\"\"Construct the scratchpad that lets the agent continue its thought process.\"\"\"\n",
    "    thoughts: List[BaseMessage] = []\n",
    "    for action, observation in intermediate_steps:\n",
    "        thoughts.append(AIMessage(content=action.log))\n",
    "        human_message = HumanMessage(\n",
    "            content=template_tool_response.format(input=query,observation=observation)\n",
    "        )\n",
    "        thoughts.append(human_message)\n",
    "    return thoughts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "94c5b783",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents.agent import AgentOutputParser\n",
    "from langchain_core.output_parsers.json import parse_json_markdown\n",
    "from langchain_core.exceptions import OutputParserException\n",
    "from langchain_core.agents import AgentAction, AgentFinish\n",
    "class JSONAgentOutputParser(AgentOutputParser):\n",
    "    \"\"\"以 JSON 格式解析工具调用和最终答案。\n",
    "\n",
    "     期望输出采用两种格式之一。\n",
    "\n",
    "     如果输出信号表明应采取行动，\n",
    "     应采用以下格式。 这将导致 AgentAction\n",
    "     被退回。\n",
    "\n",
    "    ```\n",
    "    {\n",
    "      \"action\": \"search\",\n",
    "      \"action_input\": \"2+2\"\n",
    "    }\n",
    "    ```\n",
    "    如果输出信号表明应给出最终答案，\n",
    "    应采用以下格式。 这将导致 AgentFinish\n",
    "    被退回。\n",
    "\n",
    "    ```\n",
    "    {\n",
    "      \"action\": \"Final Answer\",\n",
    "      \"answer\": \"4\"\n",
    "    }\n",
    "    ```\n",
    "    \"\"\"\n",
    "\n",
    "    def parse(self, text):\n",
    "        try:\n",
    "            response = parse_json_markdown(text)\n",
    "            if isinstance(response, list):\n",
    "                # gpt turbo frequently ignores the directive to emit a single action\n",
    "                logger.warning(\"Got multiple action responses: %s\", response)\n",
    "                response = response[0]\n",
    "            if response[\"action\"] == \"Final Answer\":\n",
    "                return AgentFinish({\"output\": response[\"answer\"]}, text)\n",
    "            else:\n",
    "                return AgentAction(\n",
    "                    response[\"action\"], response.get(\"action_input\", {}), text\n",
    "                )\n",
    "        except Exception as e:\n",
    "            raise OutputParserException(f\"Could not parse LLM output: {text}\") from e\n",
    "\n",
    "    @property\n",
    "    def _type(self) -> str:\n",
    "        return \"json-agent\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "cc6392cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from langchain.agents.output_parsers import JSONAgentOutputParser\n",
    "from langchain_core.runnables import Runnable, RunnablePassthrough\n",
    "\n",
    "\n",
    "agent = (\n",
    "        RunnablePassthrough.assign(\n",
    "            agent_scratchpad=lambda x: format_log_to_messages(\n",
    "                x[\"input\"],\n",
    "                x[\"intermediate_steps\"], \n",
    "                template_tool_response=TEMPLATE_TOOL_RESPONSE\n",
    "            )\n",
    "        )\n",
    "        | prompt\n",
    "        | llm\n",
    "        | JSONAgentOutputParser()\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "c178fe4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import AgentExecutor\n",
    "\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "02214206",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m{\n",
      "  \"action\": \"Final Answer\",\n",
      "  \"answer\": \"英国现任首相是鲍里斯·约翰逊。\"\n",
      "}\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '英国现任首相是谁？', 'output': '英国现任首相是鲍里斯·约翰逊。'}"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.invoke({\"input\": \"英国现任首相是谁？\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8237a2a8",
   "metadata": {},
   "source": [
    "## 任务计划与监督者"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "d1a347a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "planPrompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"\"\"让我们首先了解问题并制定解决问题的计划。 \n",
    "请输出以标题“计划：”开头的计划，然后是编号的步骤列表。 \n",
    "请制定准确完成任务所需的最少步骤数的计划，计划里不要写上任何答案，也不要将答案放到括号里。\n",
    "\n",
    "如果任务是一个问题，那么最后一步总是“鉴于采取了上述步骤，请回答用户最初的问题”。\n",
    "\"\"\",\n",
    "        ),\n",
    "        (\"user\", \"{input}\"),\n",
    "        #MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "8f289d22",
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "def planParse(message):\n",
    "    text = message.content\n",
    "    steps = [v for v in re.split(\"\\n\\s*\\d+\\. \", text)[1:]]\n",
    "    return steps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "2ec0e1d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "planChain = planPrompt | model | planParse"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "3b0d18f6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['收集历任美国总统的个人信息和政治生涯信息（出生日期、就职日期、主要政策等）。',\n",
       " '将每位总统的就职时年龄与他们的政策偏好进行比较，寻找趋势或模式。',\n",
       " '分析数据并得出结论，讨论年龄是否对总统的政策偏好产生影响。',\n",
       " '撰写论文或报告，包括研究方法、结果和结论。']"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "planChain.invoke({\"input\":\"写一篇关于历代美国总统的年龄跟发布政策偏好关系\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "7a8843eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptTemplate(input_variables=['input'], partial_variables={'tools': 'get_word_length: get_word_length(word: str) -> int - Returns the length of a word.\\nget_weather: get_weather(location) - 根据城市获取天气数据\\nsearch: search(keywords) - 使用duckduckgo搜索引擎获取内容'}, messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=['tools'], template='让我们首先了解用户问题或者是计划，并根据所提供的工具判断是否能直接通过工具解决问题或者计划。\\n            \\n如果需要拆分问题或者是计划，请输出以标题“计划：”开头的计划，然后是编号的步骤列表。 \\n\\n否则不需要拆分，请直接输出<End>，不用返回任何内容。\\n\\n工具：\\n{tools}\\n\\n\\n\\n')), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}'))])"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "splitTaskPrompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"\"\"让我们首先了解用户问题或者是计划，并根据所提供的工具判断是否能直接通过工具解决问题或者计划。\n",
    "            \n",
    "如果需要拆分问题或者是计划，请输出以标题“计划：”开头的计划，然后是编号的步骤列表。 \n",
    "\n",
    "否则不需要拆分，请直接输出<End>，不用返回任何内容。\n",
    "\n",
    "工具：\n",
    "{tools}\n",
    "\n",
    "\n",
    "\n",
    "\"\"\",\n",
    "        ),\n",
    "        (\"user\", \"{input}\"),\n",
    "        #MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "splitTaskPrompt = splitTaskPrompt.partial(\n",
    "        tools=render_text_description(list(tools)),\n",
    ")\n",
    "splitTaskPrompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "814c1b72",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['搜索每个总统的名字，并查看他们的维基百科页面。',\n",
       " '从维基百科页面上提取出生日期、就职日期和主要政策等信息。',\n",
       " '将收集到的信息整理成表格或文档形式。',\n",
       " '重复以上步骤，直到收集到所有历任美国总统的信息。',\n",
       " '检查收集的信息是否完整准确，如有必要进行补充修正。']"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "splitTaskChain = splitTaskPrompt | model | planParse\n",
    "splitTaskChain.invoke({\"input\":\"收集历任美国总统的个人信息和政治生涯信息（出生日期、就职日期、主要政策等）。\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "a33623d0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['使用duckduckgo搜索引擎搜索每个美国总统的名字。',\n",
       " '从搜索结果中提取出每个总统的维基百科链接。',\n",
       " '访问这些链接以获取更多信息。',\n",
       " '重复上述步骤，直到所有美国总统都被搜索到。']"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "splitTaskChain.invoke({\"input\":\"搜索每个总统的名字，并查看他们的维基百科页面。\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03fcfba7",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
